﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using NDBGen.Configuration;
using NDBGen.Plugins;
using NDBGen.Plugins.DataAccess;

namespace NDBGen
{
    internal static class DataAccess
    {
        public static void Generate(IEnumerable<SchemaDataAccessOutputElement> configs, ParsedInput input, PluginLibraries libraries)
        {
            foreach (var config in configs)
            {
                var generator = libraries.DataAccessCodeGenerators.FindOne(g => g.ConfigurationName == config.Generator);
                if (generator == null) throw new ConfigurationErrorsException(String.Format("Could not find an implementation of IDataAccessCodeGenerator named {0}", config.Generator));
                var pluginData = new DataAccessGeneratorPluginData(input);
                generator.Init(pluginData);

                IProjectFileManager projectFileManager;
                Boolean hasProject;
                String outputRootPath;
                var projectFile = ProjectManagement.LoadProjectFile(config, libraries, out projectFileManager, out hasProject, out outputRootPath);

                FileInfo outputFile = null;
                IIndentedTextWriter writer = null;

                if (!config.SeparateFiles)
                {
                    outputFile = ProjectManagement.GetOutputFile(outputRootPath, generator.GetModelOutputFilePath(config, null));
                    writer = outputFile.CreateWriter();
                }

                // generate the base context file
                if (config.GenerateBaseContext)
                {
                    if (config.SeparateFiles)
                    {
                        outputFile  = new FileInfo(Path.Combine(outputRootPath, generator.GetBaseContextOutputFilePath(config)));
                        writer = outputFile.CreateWriter();
                    }

                    generator.WriteBaseContext(config, writer);

                    if (config.SeparateFiles)
                    {
                        writer.Dispose();
                    }

                    if (hasProject) AddToProject(projectFileManager, outputFile, outputRootPath);
                    else Console.WriteLine("No project file, not adding file reference.");
                }

                var schemas = input.Tables.GroupBy(t => t.Schema);

                // Generate the Linq-to-Sql classes
                foreach (var schema in schemas)
                {
                    foreach (var table in schema)
                    {
                        if (config.SeparateFiles)
                        {
                            outputFile = new FileInfo(Path.Combine(outputRootPath, generator.GetModelOutputFilePath(config, table)));
                            writer = outputFile.CreateWriter();
                        }

                        generator.WriteModel(config, table, writer);

                        if (config.SeparateFiles)
                        {
                            writer.Dispose();
                        }

                        if (hasProject) AddToProject(projectFileManager, outputFile, outputRootPath);
                        else Console.WriteLine("No project file, not adding file reference.");
                    }

                    // this must be done after the models are generated to support the Table.WasReplacedByEnum property.
                    if (generator.CanGenerateContextType)
                    {
                        // Generate the Linq-to-Sql DB classes
                        var path = Path.Combine(outputRootPath, generator.GetContextOutputFilePath(config, schema.Key));

                        IIndentedTextWriter contextWriter = config.ModelAndContextInSameFile ? writer : new FileInfo(path).CreateWriter(!config.ModelAndContextInSameFile);
                        generator.WriteContextType(config, schema, contextWriter);
                        if (!config.ModelAndContextInSameFile) contextWriter.Dispose();

                        var relativeFilePath = path.Replace(outputRootPath, String.Empty).Trim('\\');

                        if (hasProject)
                        {
                            projectFileManager.AddFileReference(relativeFilePath);
                        }
                    }
                }

                if (hasProject)
                {
                    projectFileManager.Save();
                }
            }
        }

        static void AddToProject(IProjectFileManager projectFileManager, FileInfo outputFile, String outputRootPath)
        {
            var relativeFilePath = outputFile.FullName.Replace(outputRootPath, String.Empty).Trim('\\');
            String dependentFile;
            var pathWithNoExtension = relativeFilePath.Replace(outputFile.Extension, String.Empty);
            if (pathWithNoExtension.EndsWith(".g")) pathWithNoExtension = pathWithNoExtension.Substring(0, pathWithNoExtension.Length - 2);
            var mergeType = projectFileManager.GetMergeType(pathWithNoExtension, out dependentFile);
            switch (mergeType)
            {
                case GeneratedMergeType.DoNotGenerate: break;

                case GeneratedMergeType.GeneratedOnly:
                    projectFileManager.AddFileReference(relativeFilePath);
                    break;

                case GeneratedMergeType.GeneratePartial:
                    projectFileManager.AddFileReference(relativeFilePath, dependentFile);
                    break;
            }
        }
    }
}